package JET.geom;

import java.util.Random;

/**
 *
 * @author 057Ry
 */
public final class Vec2d
{
    public double x,y;      // wspolrzedne wektora
    public final static Vec2d zero = new Vec2d(0,0);
    public final static Vec2d one = new Vec2d(1,1);

    /**
     * Tworzy nowy wektor o zerowych wspolrzednych.
     */
    public Vec2d()
    {
        //x = 0;
        //y = 0;
    }
    
    /**
     * Tworzy nowy wektor na podstawie przekazanych wspolrzednych.
     * @param x odcieta
     * @param y rzędna
     */
    public Vec2d( double x, double y )
    {
        this.x = x;
        this.y = y;
    }
    
    /**
     * Tworzy nowy wektor na podstawie wspolrzednych z przekazanego parametru.
     * @param vec wektor, z ktory wzięte beda wsporzedne do nowego wektora.
     */
    public Vec2d( Vec2d vec )
    {
        this.x = vec.x;
        this.y = vec.y;
    }
    
    public void setX(double x) {
        this.x = x;
    }

    public void setY(double y) {
        this.y = y;
    }
    public void setXY(double x, double y)
    {
        this.x = x;
        this.y = y;
    }
    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }
    
    /**
     * Sluzy do ustawienia wspolrzednych naszego wektora na bazie wsporzednych z
     * przekazanego parametru.
     * @param vec wektor na podstawie ktorego zmieni sie nasz wektor.
     */
    public void become( Vec2d vec )
    {
        this.x = vec.x;
        this.y = vec.y;
    }
         
    /**
     * @return dlugosc wektora jako liczbe double.
     */
    public double getMagnitude()
    {
        return Math.sqrt( x*x + y*y );
    }
    public double getMagnitudeSquared()
    {
        return x*x + y*y;
    }
    /**
     * Oblicza iloczyn skalarny naszego wektora z wektorem przekazanym jako parametr.
     * @param vec
     * @return this.x*vec.x + this.y*vec.y;
     */
    public double dot( Vec2d vec )
    {
        return x*vec.x + y*vec.y ;
    }
    /**
     * Zwraca wartosc kata SKIEROWANEGO miedzy osia X a wektorem.
     * Wynik w radianach, wartosc kata w przedziale <0,2Pi)..
     * @return
     */
    public double getAngleX()
    {
        Vec2d nX = new Vec2d( 1,0 );
        Vec2d norm = new Vec2d( this );
        norm.normalize();
        double horizAngle = Math.acos( nX.dot( norm ) );
        
        if( norm.y<0 )
            return Math.PI*2.0 - horizAngle;
        else
            return horizAngle;
    }
    /**
     * Zwraca wartosc kata SKIEROWANEGO miedzy osia Y a wektorem.
     * Wynik w radianach, wartosc kata w przedziale <0,2Pi).
     * @return
     */
    public double getAngleY()
    {
        Vec2d nY = new Vec2d(0,1); // nY - normal Y
        Vec2d norm = new Vec2d( this );
        norm.normalize();
        double horizAngle = Math.acos( nY.dot( norm ) );
        
        if( norm.x>0 )
            return Math.PI*2.0 - horizAngle;
        else
            return horizAngle;
    }
    
    /**
     * Mnoży wektor przez liczbę k.
     * @param k wspólczynnik skalowania wektora.
     */
    public void scale( double k )
    {
        x *= k;
        y *= k;
    }

    public void scaleX( double k )
    {
        x *= k;
    }

    public void scaleY( double k )
    {
        y *= k;
    }

    /**
     * Zwraca wektor oddalony od pierwszej masy o k*dlugosc_wektora.
     * @param k wspolczynnik od 0 (pozycja masy1) do 1 (pozycja masy2).
     * @return new wektor.
     */
    public Vec2d getMidVec(double k) {
        double mag = getMagnitude();
        return this.getNormalized().getScaled( k*mag );
    }
    /**
     * Zwraca nowy, przeskalowany wektor.
     * @param k wspolczynnik skalowania.
     * @return przeskalowany wektor.
     */
    public Vec2d getScaled( double k )
    {
        Vec2d tmp = new Vec2d();
        tmp.add( this );
        tmp.scale( k );
        return tmp;
    }


    /**
     * Zwraca nowy wektor zmmniejszony o parametr.
     * @param v wektor odejmowany.
     * @return nowy zmniejszony wektor.
     */
    public Vec2d getSubbed(Vec2d v)
    {
        return new Vec2d( this.x-v.x, this.y-v.y);
    }

    /**
     * Zwraca nowy wektor zwiększony o parametr.
     * @param v wektor dodawany.
     * @return nowy zsumowany wektor.
     */
    public Vec2d getAdded(Vec2d v)
    {
        return new Vec2d( this.x+v.x, this.y+v.y);
    }
    /**
     * Normalizuje wektor, czyli zachowuje kierunek i zwrot, ale zmienia
     * dlugość na 1.
     */
    public void normalize()
    {
        scale( 1.0d/getMagnitude() );
    }
    
    /**
     * Zwraca nowy, znormalizowany wektor.
     * @return wersor o tym samym kierunku o zwrocie co wektor.
     */
    public Vec2d getNormalized()
    {
        Vec2d tmp = new Vec2d();
        tmp.add( this );
        tmp.normalize();
        return tmp;
    }
    
    /**
     * Miara kata skierowanego miedzy naszym wektorem a nowym-prostopadlym
     * wynosi 90 stopni (czyli prostopadl powstaje w skutek obrotu poczatkowego
     * wektora o 90 stopni w kierunku przeciwnym do ruchu wskazowek zegara.
     * 
     * @return zwraca nowy, prostopadly i znormalizowany wektor(wersor).
     */
    public Vec2d getPerp()
    {
        return new Vec2d( -y, x );
    }
    public void makePerp()
    {
        double tmpX = this.x;
        this.x = -y;
        this.y = tmpX;
    }
    /**
     * Obraca wektor przeciwnie do ruchu wskazówek zegara, o wartość kąta a.
     * @param a wartość kąta obrotu w radianach.
     */
    public void rotate(double a)
    {
        double nx = ( x*Math.cos(a) - y*Math.sin(a) );
        double ny = ( x*Math.sin(a) + y*Math.cos(a) );
        x = nx;
        y = ny;
    }
    
    /**
     * Obraca wektor przeciwnie do ruchu wskazówek zegara, o wartość kąta a,
     * dookola punktu wskazywanego przez wektor "o".
     * @param a wartość kąta obrotu w radianach.
     */
    public void rotate(double a, Vec2d o)
    {
        double nx = ( (x-o.x)*Math.cos(a) - (y-o.y)*Math.sin(a) );
        double ny = ( (x-o.x)*Math.sin(a) + (y-o.y)*Math.cos(a) );
        x = nx+o.x;
        y = ny+o.y;
    }
    
    public Vec2d getRotated(double a)
    {
        Vec2d rot = new Vec2d(this);
        rot.rotate(a);
        return rot;
    }
    /**
     * Dodaje wektor-parametr do naszego wektora.
     * @param vec wektor, który ma zostać dodany do naszego wektora.
     */
    public void add(Vec2d vec)
    {
        this.x += vec.x;
        this.y += vec.y;
    }
    public void addX(double x)
    {
        this.x += x;
    }
    public void addY(double y)
    {
        this.y += y;
    }
    public void addXY(double x, double y)
    {
        this.x += x;
        this.y += y;
    }
    /**
     * Odejmuje wektor-parametr od naszego wektora.
     * @param vec wektor, ktory ma zostać odjęty od naszego wektora.
     */
    public void sub(Vec2d vec)
    {
        this.x -= vec.x;
        this.y -= vec.y;
    }

    public void subX(double x) {
        this.x -= x;
    }
    public void subY(double y) {
        this.y -= y;
    }

    /**
     * Zeruje wektor.
     */
    public void zero() {
        this.x = 0;
        this.y = 0;
    }

    /**
     * Tworzy i zwraca losowy wektor, mieszczący się w kwadracie o boku maxLen.
     * Współrzędne wektora mogą przyjmować wartosci w przedziale  [ -maxLen; maxLen ].
     * 
     * @param maxLen maksymalna dlugosc zwracanego wektora
     * @return
     */
    public static Vec2d getRandomVectorInSquare(double maxLen)
    {
        Random random = new Random();
        return new Vec2d((random.nextDouble()*2-1)*maxLen, (random.nextDouble()*2-1)*maxLen);
    }
    /**
     * Tworzy i zwraca losowy wektor, mieszczący się w okręgu o promieniu maxLen.
     * Współrzędne wektora mogą przyjmować wartosci w przedziale [ -maxLen; maxLen ].
     *
     * @param maxLen maksymalna dlugosc zwracanego wektora
     * @return
     */
    public static Vec2d getRandomVectorInCircle(double maxLen)
    {
        Random random = new Random();
        return (new Vec2d(0, (random.nextDouble()*2-1)*maxLen)).getRotated( random.nextDouble()*Math.PI*2 );
    }
    @Override
    public String toString()
    {
        return "["+x+", "+y+"]";
    }
    public boolean equals(Vec2d vec)
    {
        if ( x==vec.getX() && y==vec.getY() )
            return true;
        return false;
    }
    public static double dot(Vec2d a, Vec2d b)
    {
        return a.x*b.x + a.y*b.y;
    }
    public static double crossZ(Vec2d a, Vec2d b) {
        return a.x*b.y - a.y*b.x;
    }
    
    public static Vec3d cross(Vec2d a, Vec2d b) {
//        return new Vec3d( a.y*0 - 0*b.y,
//                          0*b.x - a.x*0,
//                          a.x*b.y - a.y*b.x );
        return new Vec3d( 0,
                          0,
                          a.x*b.y - a.y*b.x );
    }
    
    /* Returns true if a is rotating to b in counter-clockwise order. */
    public static boolean isCCW(Vec2d a, Vec2d b)
    {
        Vec2d perp = a.getPerp();
        double dot = Vec2d.dot(b,perp);
        return (dot >= 0.0f);
    }
}
